package com.shudes.util;

import java.lang.reflect.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;

public class Dumper {
	public static final Dumper INSTANCE = new Dumper();

	protected Set<Class> primitives;
	protected final SimpleDateFormat DATE = Dates.TIMESTAMP;
	protected final String NULL = "[null]";
	protected final String[] SET_DELIM = new String[] {"{", "}" };
	protected final String EQ = "=";
	protected final String COMMA = ","; 
	protected final int DEFAULT_DEPTH=1;
	
	public Dumper() {
		initPrimitives();
	}

	public String dumpFlat(Object object) {
		Map map;
		
		map = this.dumpImpl(object, this.DEFAULT_DEPTH);
		return flattenMap(map, "");
	}
	
	public String dumpAllFlat(Collection c) { 
		String result="";
		for(Object o:c) { 
			result += this.dumpFlat(o) + "\n"; 
		}
		return result; 
	}
		
	public String dumpSideways(Object object) {
		Map map;
		
		map = this.dumpImpl(object, this.DEFAULT_DEPTH);
		return this.sidewaysTreeMap(map,"",0);		
	}
	
	public String dumpAllSideways(Collection c) {
		String result=""; 
		for(Object o : c) { 
			result += this.dumpSideways(o) + "\n"; 
		}
		return result; 
	}
	
	public String dump(Object o) { 
		return dumpFlat(o);
	}
	
	
	public String nullToString(Object o) {
		return (o==null) ? "[null]" : o.toString();
	}

	public String dumpClassName(Object o) {
		Class c;
		String name;
			
		c = (o instanceof Class) ? (Class) o : o.getClass();		
		name = c.getName();

		return name.substring(name.lastIndexOf(".")+1); 
	}	
	
	private void initPrimitives() {
		primitives = new HashSet<Class>();
		primitives.add(Integer.class);
		primitives.add(String.class);
		primitives.add(Double.class);
		primitives.add(Long.class);
		primitives.add(Date.class);
		primitives.add(Class.class);
		primitives.add(java.sql.Timestamp.class);
	}
	
	protected Map dumpImpl(Object object, int maxDepth)
	{
		return dumpImpl(object, maxDepth, 0);
	}
	
	protected Map<Object, Object> dumpImpl(Object obj, int maxDepth, int currentDepth)
	{
		Class c;
		Pattern p;
		Matcher m;
		Map<Object, Object> map;
		
		map = new HashMap<Object, Object>();		

		if(obj==null) {
			map.put(this.NULL, null);
			return map;
		}
		
	    if(primitives.contains(obj.getClass())) {
	    	map.put(dumpPrimitive(obj),null);
	    	return map;
	    }
	    	    		    	    
	    c = obj.getClass();
	    p = Pattern.compile("^get");
		    
	    Method[] methods = c.getMethods();
	    for(Method meth : methods) {
	      m = p.matcher(meth.getName());
	      if(m.find() && meth.getParameterTypes().length==0) {
	        if("getClass".equals(meth.getName())) {	        		        		        	
	        	map.put("class", dumpClassName(obj.getClass()));
	        } else {          	  
	        	String name=meth.getName().substring(3);
	        	Object value = valueOf(obj, meth);
	        	if(value==null || primitives.contains(value.getClass()) || maxDepth==currentDepth)
	        		map.put(name, value);
	        	else
	        		map.put(name, dumpImpl(value, maxDepth, currentDepth+1));
	        }
	      }
	    }

	    return map;		
	}
	
	protected String flattenMap(Map map, String current) {
		Object value;
		
		for(Object key:map.keySet()) {
			value = map.get(key);
			
			if(value==null)  {				
				current +=  key.toString() + this.COMMA;
			} else if((!(value instanceof Map))) {
				current += key.toString() + this.EQ  + dumpPrimitive(value) + this.COMMA;
			} else {			
				current +=  key.toString() + this.EQ + this.SET_DELIM[0] + flattenMap((Map) value, current)+ this.SET_DELIM[1] + this.COMMA; 
			}
									
		}
		
		return current;
	}
	
	protected String sidewaysTreeMap(Map map, String current, int indentCount)
	{
		// each map is a tabcount
		String tab = "";
		Object value; 
		
		for(int i=0; i<indentCount;i++)
			tab += "\t";
		
		if(map.containsKey("class")) {
			current += "\n" + tab + "class=" + map.get("class");
			map.remove("class"); 
		}
		
		if(indentCount==0) {
			indentCount++;
			tab += "\t";
		}
		
		for(Object key : map.keySet()) {
			value = map.get(key);
			
//			System.out.println("---[current]---"); 
//			System.out.println("key=" + key + "\tvalue=" + value);
			
			if(value instanceof Map) {
				String c = "\n" + tab + ((Map) value).get("class") + "=";				
				current += sidewaysTreeMap((Map) value, c, indentCount+1);
			} else {
				current += "\n" + tab + key + "=" + value;
			}
			

//			System.out.println(current);
//			System.out.println("------------------");
			
		}						
		
		return current;		
	}		
		
	protected String dumpPrimitive(Object o) {
		if(o==null) 
			return this.NULL;
		
		if(o instanceof Date) 
			return 	DATE.format((Date) o);
				
		if(o instanceof java.sql.Timestamp) {
			long l = ((java.sql.Timestamp) o).getTime();
			return DATE.format(new Date(l));
		}			
		
		if(primitives.contains(o.getClass())) 
			return o.toString();
		
		return dumpClassName(o.getClass());
	}
	
	private Object valueOf(Object obj, Method meth)
	{
		try {
	      Object result = meth.invoke(obj, new Object[0]);
	      return result;
	    } catch(IllegalAccessException e1) {      
	    } catch(InvocationTargetException e2) {       
	    } 
	    
		return null;    
	}
		

}
